package com.orchidea.uml.manager
{
import com.orchidea.uml.events.UndoEvent;
import com.orchidea.uml.manager.command.AddEntityCommand;
import com.orchidea.uml.manager.command.ICommand;
import com.orchidea.uml.manager.command.IUndoable;
import com.orchidea.uml.manager.command.LinkEntitiesCommand;
import com.orchidea.uml.manager.command.UpdateDocumentCommand;
import com.orchidea.uml.model.UmlEntityType;
import com.orchidea.uml.model.document.Document;
import com.orchidea.uml.model.document.Entity;
import com.orchidea.uml.orchidea_internal;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IEventDispatcher;

import mx.collections.ICollectionView;

/**
	 * The DocumentManager manages a single document. It provides a transactional interface
	 * for manipulating that document, making sure that actions are undoable, and keeping
	 * track of whether or not the document needs saving or not (i.e. if it's "dirty").
	 *
	 * All modifications of the managed document are done in commands, and a change is
	 * undoable the particular command implements the Undoable interface. After an undoable
	 * command has been executed it is reported to the undo system.
	 *
	 * Currently there is only one action that modifies the document: update, which has the
	 * corresponding command UpdateDocumentCommand.
	 */
    public class DocumentManager extends EventDispatcher
    {
        /*
		 * All methods on the Document class that are destructive (i.e. changes the object's state)
		 * are marked with the app_internal namespace. This means that any code that wants to modify
		 * a document needs to open that namespace somehow. This makes it easier to keep track on
		 * where destructive changes are made.
		 */
		use namespace orchidea_internal;

        private var _document : Document;
        [Bindable(event="documentChanged")]
        public function get document( ) : Document { return _document; }

        private var _documentDirty : Boolean;
        /**
         * Returns whether or not the managed document has unsaved changes.
         */
        [Bindable(event="documentDirtyChanged")]
        public function get documentDirty( ) : Boolean { return _documentDirty; }

        private var _dispatcher : IEventDispatcher;
        /**
         * The DocumentManager needs a way to dispatch events to the local controller
         * via an event dispatcher, this is where that dispatcher is injected.
         */
        public function set dispatcher( value : IEventDispatcher ) : void { _dispatcher = value; }


        /**
         * Returns a collection of all open documents.
         */
        [Bindable(event="elementsChanged")]
        public function get elements() : ICollectionView
        {
            return _document.elements;
        }


		/** Internal method for dispatching the "documentChanged" event. */
		private function dispatchDocumentChanged( ) : void { dispatchEvent(new Event("documentChanged")); }

		/** Internal method for dispatching the "documentDirtyChanged" event. */
		private function dispatchDocumentDirtyChanged( ) : void { dispatchEvent(new Event("documentDirtyChanged")); }

		/** Listener for the Event.CHANGE event on the managed Document instance. */
		private function onDocumentChange( event : Event ) : void
        {
			_documentDirty = true;
			dispatchDocumentDirtyChanged();
		}

		/**
		 * Sets the document instance that this manager manages, this triggers the
		 * dispatch of both the "documentChanged" and "documentDirtyChanged" events.
		 */
		public function setDocument( d : Document ) : void
        {
			if ( _document != null )
            {
				_document.removeEventListener(Event.CHANGE, onDocumentChange);
			}

			_document      = d;
			_documentDirty = false;

			if ( _document != null )
            {
				_document.addEventListener(Event.CHANGE, onDocumentChange);
			}

			dispatchDocumentChanged();
			dispatchDocumentDirtyChanged();
		}

        /**
		 * After a document has been saved (an action that this manager is unaware of),
		 * this method needs to be called to unset the dirty flag.
		 */
		public function documentSaved( ) : void
        {
            _documentDirty = false;
            dispatchDocumentDirtyChanged();
        }


		/**
		 * Modifes the managed document by setting a new title. This action is undoable.
		 */
		public function update(title:String):void
        {
			exec(new UpdateDocumentCommand(_document, title));
		}


        /**
		 * Modifes the managed document by adding a new element. This action is undoable.
		 */
        public function createNewEntity(type:UmlEntityType):void
        {
            var e:Entity = new Entity(type);
            exec(new AddEntityCommand(_document, e));
        }

        /**
		 * Modifes the managed document by making a linkage between two elements. This action is undoable.
		 */
        public function linkEntities(e1:Entity, e2:Entity):void
        {
            exec(new LinkEntitiesCommand(_document, e1, e2));
        }


		/**
		 * Executes the specified command by calling its execute method. Also, and more
		 * importantly, it checks if that command is undoable. Undoable commands are
		 * reported to the undo system by dispatching the UndoEvent.ADD_UNDOABLE event.
		 */
		private function exec( command : ICommand ) : void
        {
			command.execute();

			if ( command is IUndoable )
            {
				_dispatcher.dispatchEvent(new UndoEvent(UndoEvent.ADD_UNDOABLE, IUndoable(command)));
			}
		}
    }
}